/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.content;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.common.util.ConvertUtil;
import com.blandware.atleap.common.util.StringUtil;
import com.blandware.atleap.model.core.ContentField;
import com.blandware.atleap.model.core.ContentFieldValue;
import com.blandware.atleap.model.core.Page;
import com.blandware.atleap.service.core.ContentFieldManager;
import com.blandware.atleap.service.core.PageManager;
import com.blandware.atleap.webapp.struts.ContentTilesRequestProcessor;
import com.blandware.atleap.webapp.struts.HeritableComponentDefinition;
import com.blandware.atleap.webapp.taglib.core.util.ContextMenuItem;
import com.blandware.atleap.webapp.taglib.core.util.JavaScriptUtil;
import com.blandware.atleap.webapp.util.core.ApplicationResources;
import com.blandware.atleap.webapp.util.core.CacheUtil;
import com.blandware.atleap.webapp.util.core.WebappConstants;
import com.blandware.atleap.webapp.util.core.WebappUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.GenericValidator;
import org.apache.struts.Globals;
import org.apache.struts.taglib.TagUtils;
import org.apache.struts.tiles.ComponentDefinition;
import org.apache.struts.tiles.TilesUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.util.Arrays;
import java.util.Locale;

/**
 * <p>Tag used to include content field value from database by identifier. It may
 * also be highlighted in edit mode when a mouse is over it and may be edited
 * through context menu or double click. Context menu gives ability to edit
 * content field value for current locale (this is what user gets when he
 * double-clicks on content), for all locales or for selected locales only.</p>
 * <p>If field with given identifier is indexed, then in edit mode context menu
 * will contain another two items which allow to create a new index (i.e.
 * indexed field with same identifier but another index) and delete an existing
 * index.</p>
 * <p>Firstly, a set of content fields is calculated: those are all fields that
 * belong to given definition and page with given URI. Then, among them, field
 * with given identifier is taken. After that, a content field value for content
 * locale with given locale identifier is treated as result.</p>
 * <p><em>If URI is specified explicitly (through <b>uri</b> attribute), only
 * fields from page are considered: nothing is searched on layouts.</em></p>
 * <p>If edit mode is on, <b>editable</b> is "true" (or omitted) and CFV couldn't
 * be found or empty, then message like "The field value is empty, please insert
 * the text" will be treated as result.</p>
 * <p>Note, that when this tag looks for a field through definitions, it ALWAYS
 * skips starting definition. So, if you have a parent_definition and
 * child_definition and <b>definition</b> attribute is set to be
 * 'child_definition' (or this is definition on which current page is based),
 * then only fields from parent_definition and its parents will be considered
 * when obtaining content.</p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>identifier</b> - mandatory identifier of the content field
 * </li>
 * <li>
 * <b>uri</b> - URI of the page in database. By default it is URI of currently processed page.
 * If this attribute is specified, fields are not searched on layouts.
 * </li>
 * <li>
 * <b>definition</b> - Definition name of some layout. By default it is definition of currently processed page.
 * </li>
 * <li>
 * <b>locale</b> - By default locale is taken from session
 * </li>
 * <li>
 * <b>ignore</b> - "true" if you do not want to issue an error message when content was not found,
 * default is "false"
 * </li>
 * <li>
 * <b>filter</b> - whether or not to replace characters, which are sensitive in
 * HTML, with their entity equivalents. If not specified, then data will be
 * filtered only if field type is not <code>ContentField.HTML_TYPE</code>.
 * </li>
 * <li>
 * <b>contextMenuJsp</b> - optional - path to JSP which will render a context menu. If not
 * given, <code>WebappConstants.DEFAULT_CONTEXT_MENU_JSP</code> is used.
 * </li>
 * <li>
 * <b>showEmpty</b> - If "true", empty content will be shown, otherwise it will be
 * searched further in sequence of layouts (from child to parent). If not
 * specified, then this is treated as "true" if and only if field type is not
 * <code>ContentField.LINE_TYPE</code>.
 * </li>
 * <li>
 * <b>var</b> - name of variable that will accept the result
 * </li>
 * <li>
 * <b>scope</b> - scope of variable that will accept the result
 * </li>
 * <li>
 * <b>index</b> - field index if this tag is used used inside
 * <em>contentIterator</em> tag.
 * </li>
 * <li>
 * <b>editable</b> - whether this content field value is editable by user in
 * edit mode, default is "true"
 * </li>
 * <li>
 * <b>wrapperDisplay</b> - display value to use for wrapper DIV in edit mode.
 * This can be "block", "inline" and "auto" (any other value has same effect as
 * "auto"). Default is "auto".
 * <br/>
 * If this is "auto", then display value will be determined on field type:
 * for <code>ContentField.LINE_TYPE</code> it's "inline", for others it's
 * "block". 
 * </li>
 * <li>
 * <b>fieldType</b> - Type of field to be created if no field was found
 * in edit mode and user is allowed to create a field with double-click.
 * This attribute is used ONLY when field is created
 * by double-click, not when content is obtained. This can be one of following:
 * ContentField.LINE_TYPE, ContentField.MULTILINE_TYPE, ContentField.HTML_TYPE.
 * Default is ContentField.HTML_TYPE.
 * </li>
 * </ul>
 * <p>This can be used only on pages processed by action using tile definition.</p>
 * <p>
 * Here's an example:
 * <pre>
 * &lt;atleap:contentIterator identifier="leftBlock" index="index"&gt;
 *     &lt;div class="block"&gt;
 *         &lt;atleap:content identifier="leftBlock" index="${index}"/&gt;
 *     &lt;/div&gt;
 * &lt;/atleap:contentIterator&gt;
 * </pre>
 * It will render content field values that belong to indexed field with
 * identifier "leftBlock", which is owned by current page or its layout for
 * current content locale.
 * </p>
 * <p><a href="ContentTag.java.html"><i>View Source</i></a></p>
 * <p/>
 *
 * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.58 $ $Date: 2008/01/14 11:19:19 $
 * @jsp.tag name="content"
 * body-content="empty"
 */
public class ContentTag extends SimpleTagSupport {

	protected transient final Log log = LogFactory.getLog(ContentTag.class);
	protected ApplicationContext applicationCtx = null;
	protected static final String NUMBER_KEY = "com.blandware.atleap.taglib.content.CONTENT_TAG_NUMBER";

    /**
     * Block display
     */
    public static final String DISPLAY_BLOCK = "block";
    /**
     * Inline display
     */
    public static final String DISPLAY_INLINE = "inline";
    /**
     * Auto display detection (detected by field type)
     */
    public static final String DISPLAY_AUTO = "auto";

    /**
	 * Name of variable to export field value
	 */
	protected String var;

	/**
	 * Scope to export variable to
	 */
	protected String scope;
    /**
     * Mandatory identifier of the content field
     */
	protected String identifier = null;
    /**
     * Definition name of some layout. By default it is definition of currently processed page.
     */
	protected String definition = null;
    /**
     * URI of the page in database. By default it is URI of currently processed page.
     */
	protected String uri = null;
    /**
     * By default locale is taken from session
     */
	protected String locale = null;
    /**
     * Field index if this tag is used used inside <em>contentIterator</em> tag.
     */
	protected String index = null;
    /**
     * "true" if you do not want to issue an error message when content was not found,
     * default is "false"
     */
	protected Boolean ignore = Boolean.FALSE;
    /**
     * Path to JSP which will render a context menu. If not
     * given, <code>WebappConstants.DEFAULT_CONTEXT_MENU_JSP</code> is used.
     */
	protected String contextMenuJsp = null;


	/**
	 * If <code>true</code> empty content will be shown, otherwise it will be searched in sequence of layouts
	 */
	protected Boolean showEmpty;

	/**
	 * Whether or not to replace characters, which are sensitive in HTML, with their entity equivalents.<br />
	 */
	protected Boolean filter = null;

	/**
	 * If this value is <code>Boolean.TRUE</code> content will be wrapped in &lt;span&gt;&lt;/span&gt; tags
	 * to provide access to editing content while in edit mode.
	 */
	protected Boolean editable = Boolean.TRUE;

    /**
     * Display to use when outputting wrapper block
     */
    protected String wrapperDisplay = DISPLAY_AUTO;

    /**
     * Type of field to use when creating new field
     */
    protected Byte fieldType = null;

    /**
     * Returns identifier of the field
     *
     * @return identifier of the field
	 * @jsp.attribute required="true"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Identifier of the field"
	 */
	public String getIdentifier() {
		return identifier;
	}

    /**
     * Sets identifier of the field
     *
     * @param identifier identifier of the field to set
     */
	public void setIdentifier(String identifier) {
		this.identifier = identifier;
	}

	/**
     * Returns URI of page to which the field belongs
     *
     * @return URI of page
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="URI of the page in database. By default it is uri of current processed page."
	 */
	public String getUri() {
		return uri;
	}

    /**
     * Sets URI of page to which the field belongs
     *
     * @param uri URI of page to set
     */
	public void setUri(String uri) {
		this.uri = uri;
	}

	/**
     * Returns tiles definition name to which the field belongs
     *
     * @return tiles definition name to which the field belongs
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Definition name in database. By default it is definition of current processed page."
	 */
	public String getDefinition() {
		return definition;
	}

    /**
     * Sets tiles definition name to which the field belongs
     *
     * @param definition tiles definition name to set
     */
	public void setDefinition(String definition) {
		this.definition = definition;
	}

	/**
     * Returns whether to ignore search errors
     *
     * @return whether to ignore search errors
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="If it is true then search errors will be ignored"
	 */
	public Boolean getIgnore() {
		return ignore;
	}

    /**
     * Sets whether to ignore search errors
     *
     * @param ignore whether to ignore search errors
     */
	public void setIgnore(Boolean ignore) {
		this.ignore = ignore;
	}

	/**
     * Returns whether to show empty value
     *
     * @return whether to show empty value
     * @see #showEmpty
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="If true empty content will be shown, otherwise it will be searched in sequence of layouts "
	 */
	public Boolean getShowEmpty() {
		return showEmpty;
	}

    /**
     * Sets whether to show empty value
     *
     * @param showEmpty whether to show empty value
     * @see #showEmpty
     */
	public void setShowEmpty(Boolean showEmpty) {
		this.showEmpty = showEmpty;
	}

	/**
     * Returns whether or not to replace characters, which are sensitive in
     * HTML, with their entity equivalents.
     *
	 * @return whether or not to replace characters, which are sensitive in HTML
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether or not to replace characters, which are sensitive in HTML, with their entity equivalents"
	 */
	public Boolean getFilter() {
		return filter;
	}

    /**
     * Sets whether or not to replace characters, which are sensitive in HTML,
     * with their entity equivalents.
     *
     * @param filter whether or not to replace characters, which are sensitive in HTML
     */
	public void setFilter(Boolean filter) {
		this.filter = filter;
	}

	/**
     * Returns locale for which to obtain CFV
     *
     * @return locale
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="By default locale is used form session"
	 */
	public String getLocale() {
		return locale;
	}

    /**
     * Sets locale for which to obtain CFV
     *
     * @param locale locale to set
     */
	public void setLocale(String locale) {
		this.locale = locale;
	}

	/**
     * Returns name of variable that will accept the result
     *
     * @return name of variable
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of variable to export message"
	 */
	public String getVar() {
		return var;
	}

    /**
     * Sets name of variable that will accept the result
     *
     * @param var name of variable to set
     */
	public void setVar(String var) {
		this.var = var;
	}

	/**
     * Returns variable scope
     *
     * @return variable scope
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope to export variable to"
	 */
	public String getScope() {
		return scope;
	}

    /**
     * Sets variable scope
     *
     * @param scope variable scope to set
     */
	public void setScope(String scope) {
		this.scope = scope;
	}

	/**
     * Returns field index
     *
     * @return field index
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Index if this tag is used used inside ContentIterator"
	 */
	public String getIndex() {
		return index;
	}

    /**
     * Sets field index
     *
     * @param index field index to set
     */
	public void setIndex(String index) {
		this.index = index;
	}

	/**
     * Returns whether this value will be editable by user
     *
	 * @return whether this value will be editable by user
     * @see #editable
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Is content editable or not"
	 */
	public Boolean getEditable() {
		return editable;
	}

    /**
     * Sets whether this value will be editable by user
     *
     * @param editable whether this value will be editable by user
     * @see #editable
     */
	public void setEditable(Boolean editable) {
		this.editable = editable;
	}

	/**
     * Returns address of JSP that will be used to render a context menu
     *
	 * @return address of JSP for context menu
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Path to JSP which will render a context menu"
	 */
	public String getContextMenuJsp() {
		return contextMenuJsp;
	}

    /**
     * Sets address of JSP that will be used to render a context menu
     *
     * @param contextMenuJsp address of JSP for context menu to set
     */
	public void setContextMenuJsp(String contextMenuJsp) {
		this.contextMenuJsp = contextMenuJsp;
	}

    /**
     * Returns display for wrapper. If it's 'auto', display will be determined
     * automatically by field type.
     *
     * @return wrapper display
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Display for wrapper; can be 'block', 'inline' or 'auto' for automatic detection"
     */
    public String getWrapperDisplay() {
        return wrapperDisplay;
    }

    /**
     * Sets display for wrapper. If it's 'auto', display will be determined
     * automatically by field type.
     *
     * @param wrapperDisplay value to set
     */
    public void setWrapperDisplay(String wrapperDisplay) {
        this.wrapperDisplay = wrapperDisplay;
    }

    /**
     * Returns type of field to use when creating new field (in edit mode).
     *
     * @return field type
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Byte"
	 * description="Type of field to use when creating new field (in edit mode)"
     */
    public Byte getFieldType() {
        return fieldType;
    }

    /**
     * Sets type of field to use when creating new field (in edit mode).
     *
     * @param fieldType type to set
     */
    public void setFieldType(Byte fieldType) {
        this.fieldType = fieldType;
    }

    /**
     * Processes the tag
     *
     * @throws JspTagException
     */
	public void doTag() throws JspTagException {

		PageContext pageContext = (PageContext) getJspContext();

		HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
		ApplicationResources applicationResources = ApplicationResources.getInstance(pageContext.getServletContext());
		ServletContext servletContext = pageContext.getServletContext();

		if ( ignore == null ) {
			ignore = Boolean.FALSE;
		}

		if ( applicationCtx == null ) {
			applicationCtx = WebApplicationContextUtils.getRequiredWebApplicationContext(pageContext.getServletContext());
		}

        String indexlessIdentifier = identifier;
        boolean indexed = false;
        if ( index != null && index.trim().length() > 0 ) {
			identifier = identifier + "[" + index + "]";
            indexed = true;
        }

		// calculate tag number
		Integer tagNumber = (Integer) pageContext.getAttribute(NUMBER_KEY, PageContext.REQUEST_SCOPE);
		if ( tagNumber == null ) {
			tagNumber = new Integer(0);
		} else {
			tagNumber = new Integer(tagNumber.intValue() + 1);
		}

		pageContext.setAttribute(NUMBER_KEY, tagNumber, PageContext.REQUEST_SCOPE);

		//get locale
		if ( locale == null ) {
			Locale l = (Locale) pageContext.getAttribute(Globals.LOCALE_KEY, PageContext.SESSION_SCOPE);
			if ( l != null ) {
				locale = l.getLanguage();
			}
		}
		if ( locale == null ) {
			locale = Locale.getDefault().getLanguage();
		}

		// get definition
		if ( definition != null ) {
			ComponentDefinition tmpDef = null;
			try {
				tmpDef = TilesUtil.getDefinition(definition, request, servletContext);
			} catch ( Exception ex ) {
				//do nothing
			}
			if ( tmpDef == null ) {
				if ( ignore.booleanValue() ) {
					return;
				}
				String errorMessage = "Specified definition '" + definition + "' has not been found";
                throw new JspTagException(errorMessage);
			}
		} else {
			definition = (String) pageContext.getAttribute(ContentTilesRequestProcessor.DEFINITION_NAME, PageContext.REQUEST_SCOPE);
		}
		if ( definition == null ) {
			if ( ignore.booleanValue() ) {
				return;
			}
			String errorMessage = "DEFINITION_NAME attribute has not been found in request. This can be used only on pages processed by action using tile definition.";
            throw new JspTagException(errorMessage);
		}

		//get uri
        boolean uriGiven = uri != null;
        if ( uri == null ) {
			uri = (String) pageContext.getAttribute(ContentTilesRequestProcessor.PROCESSED_URI, PageContext.REQUEST_SCOPE);
		}

		if ( uri == null ) {
			if ( ignore.booleanValue() ) {
				return;
			}
			String errorMessage = "PROCESSED_URI attribute has not been found in request. This can be used only on pages processed by action using tile definition.";
            throw new JspTagException(errorMessage);
		}

        if (!DISPLAY_BLOCK.equals(wrapperDisplay)
                && !DISPLAY_INLINE.equals(wrapperDisplay)) {
            wrapperDisplay = DISPLAY_AUTO;
        }

        // save definition and uri for later usage
		String savedDefinition = definition;
		String savedUri = uri;

		TagUtils tagUtils = TagUtils.getInstance();
		if ( GenericValidator.isBlankOrNull(contextMenuJsp) ) {
			contextMenuJsp = WebappConstants.DEFAULT_CONTEXT_MENU_JSP;
		}
		try {
			if ( log.isDebugEnabled() ) {
				log.debug("Processing content; definition name: " + definition + ", locale: " + locale + ", uri: " + uri + ", identifier: " + identifier);
			}
			String content = null;
			Long fieldId = null;
			Long fieldValueId = null;
			byte fieldType = 0;
            Long ownerId = null;
            boolean fieldNotFound = false;

            CacheUtil cacheUtil = CacheUtil.getInstance(request);
			CacheUtil.CFVData cfvData;

			//try to search in cache by uri
			cfvData = cacheUtil.getPageFieldValueFromCache(uri, identifier, locale);

			if ( cfvData != null ) {
				content = cfvData.getData();
				fieldId = cfvData.getContentFieldId();
				fieldType = cfvData.getContentFieldType();
                ownerId = cfvData.getOwnerId();
            }

			// rules to determine, whether or not to show empty content
			// 1. if this.showEmpty is specified, get its value
			// 2. else check field's type
			// 3. if field's type is LINE_TYPE, set local showEmpty to true, otherwise, set it to false
			boolean showEmpty = this.showEmpty == null ? fieldType != ContentField.LINE_TYPE : this.showEmpty.booleanValue();

			// not found in cache, search in persistence layer
			if ( cfvData == null || (content != null && content.trim().length() == 0 && !showEmpty) ) {
				// try to search content by uri
				if ( log.isDebugEnabled() ) {
					log.debug("Trying to search in persistent storage by uri: " + uri);
				}
				ContentFieldManager contentFieldManager = (ContentFieldManager) applicationCtx.getBean(Constants.CONTENT_FIELD_MANAGER_BEAN);

				// search content field by page uri
				boolean continueSearch = true;
				ContentField field = contentFieldManager.findContentFieldByPageUri(identifier, uri);
				content = null;
				if ( field != null ) {
					ContentFieldValue fieldValue = contentFieldManager.findContentFieldValueByUriAndIdentifierAndLocale(uri, identifier, locale);
					if ( fieldValue == null ) {
                        // field is found, so stop searching
                        continueSearch = false;
					} else {
						fieldValueId = fieldValue.getId();
						content = retrieveContent(fieldValue, field.getType());
						showEmpty = this.showEmpty == null ? field.getType() != ContentField.LINE_TYPE : this.showEmpty.booleanValue();
						continueSearch = (content == null || content.trim().length() == 0) && !showEmpty;
					}
				}

				if ( continueSearch && !uriGiven ) {
					// not found - search by layout definition
					String previousDefinition;
					do {
						previousDefinition = definition;
						definition = ((HeritableComponentDefinition) TilesUtil.getDefinition(definition, request, servletContext)).getExtends();
						if ( definition != null ) {
							if ( log.isDebugEnabled() ) {
								log.debug("Trying to search in persistent storage by definition: " + definition);
							}
							field = contentFieldManager.findContentFieldByLayoutDefinition(identifier, definition);
							if ( field != null ) {
								// field has been found, search for CFV
								ContentFieldValue fieldValue = contentFieldManager.findContentFieldValueByDefinitionAndIdentifierAndLocale(definition, identifier, locale);
								if ( fieldValue == null ) {
                                    // field is found, so stop searching
                                    continueSearch = false;
								} else {
									fieldValueId = fieldValue.getId();
									content = retrieveContent(fieldValue, field.getType());
									showEmpty = this.showEmpty == null ? field.getType() != ContentField.LINE_TYPE : this.showEmpty.booleanValue();
									continueSearch = (content == null || content.trim().length() == 0) && !showEmpty;
								}
							} else {
								continueSearch = true;
							}
						}
					} while ( definition != null && continueSearch );

					if ( field != null ) {
						fieldId = field.getId();
						fieldType = field.getType();
                        ownerId = field.getOwner().getId();

                        // put in cache
                        cfvData = new CacheUtil.CFVData(content, fieldId, fieldType, fieldValueId, ownerId);
                        if ( definition == null ) {
                            cacheUtil.putLayoutFieldValueInCache(cfvData, previousDefinition, uri, identifier, locale);
                        } else {
                            cacheUtil.putLayoutFieldValueInCache(cfvData, definition, uri, identifier, locale);
                        }
					}
				} else if ( field != null ) {
                    // field is found on page
                    fieldId = field.getId();
					fieldType = field.getType();
                    ownerId = field.getOwner().getId();

                    // put content in cache
                    cfvData = new CacheUtil.CFVData(content, fieldId, fieldType, fieldValueId, ownerId);
                    cacheUtil.putPageFieldValueInCache(cfvData, uri, identifier, locale);
				}
				if ( field == null ) {
                    fieldNotFound = true;
                }
			}

            if ( fieldId == null && uriGiven ) {
                // nothing was found, check maybe there's no page with given URI
                PageManager pageManager = (PageManager) applicationCtx.getBean(Constants.PAGE_MANAGER_BEAN);
                Page page = pageManager.findPageByUri(uri);
                if ( page == null ) {
                    if ( ignore.booleanValue() ) {
                        return;
                    }
                    String errorMessage = "Specified page uri '" + uri + "' has not been found";
                    throw new JspTagException(errorMessage);
                }
		    }

            boolean contentIsEmpty = content == null || content.trim().length() == 0;

			if ( !contentIsEmpty ) {
				boolean encode = filter == null ? fieldType != ContentField.HTML_TYPE : filter.booleanValue();

				if ( encode ) {
					content = StringUtil.htmlEncode(content);
				}
			}

			if ( var != null ) {
				if ( content != null ) {
					// save message in specified scope
					int varScope = PageContext.PAGE_SCOPE;
					if ( scope != null ) {
						varScope = tagUtils.getScope(scope);
					}
					pageContext.setAttribute(var, content, varScope);
				}
			} else {
				// write message directly to page
				Object editModeEnabledAttr = request.getSession().getAttribute(WebappConstants.SITE_EDIT_MODE_ENABLED_KEY);
				boolean editModeEnabled = editModeEnabledAttr != null && Boolean.TRUE.equals(editModeEnabledAttr);
                Long contentPageId = (Long) request.getAttribute(ContentTilesRequestProcessor.CONTENT_PAGE_ID);
                if ( editModeEnabled && editable != null && editable.booleanValue() && !(fieldNotFound && contentPageId == null) ) {
                    if (fieldNotFound) {
                        content = applicationResources.getMessage(request, "core.commons.contentTag.fieldNotFound", new Object[]{identifier, savedUri, savedDefinition});
                    } else if ( contentIsEmpty ) {
						content = applicationResources.getMessage(request, "core.commons.contentTag.insertText");
					}
                    // wrap content with <div></div> tags
					StringBuffer divId = new StringBuffer("__field__value__").append(fieldId).append("__wrapper__").append(tagNumber);
					String requestUrl = (String) request.getAttribute(ContentTilesRequestProcessor.PROCESSED_URL);
					if ( requestUrl == null ) {
						requestUrl = WebappUtil.findGlobalForwardConfig("index", null, request).getPath();
					}

					// Make context menu ID
					StringBuffer contextMenuId = new StringBuffer("__contextMenu__content__").append(tagNumber);

					/*
					   That's a name of JS variable that will hold this
					   context menu (that variable is used to refer to this context
					   menu from showContextMenu(), for instance)
					*/
					StringBuffer contextMenuLayerVar = new StringBuffer(contextMenuId.toString());

					StringBuffer onmouseover = new StringBuffer("doSelectLayer(this.id);");
					StringBuffer onmouseout = new StringBuffer("doUnselectLayer(this.id);");
					StringBuffer ondblclick;
                    if (fieldNotFound) {
                        // computing field type
                        if (getFieldType() == null
                                || (getFieldType().byteValue() != ContentField.LINE_TYPE
                                && getFieldType().byteValue() != ContentField.MULTILINE_TYPE
                                && getFieldType().byteValue() != ContentField.HTML_TYPE)) {
                            setFieldType(new Byte(ContentField.HTML_TYPE));
                        }

                        ondblclick = new StringBuffer("doCallFieldUpdateInCurrentLocale('")
                                .append(locale).append("', ")
                                .append(fieldId).append(", '")
                                .append(requestUrl).append("', ")
                                .append(contentPageId).append(", '")
                                .append(identifier).append("', ")
                                .append(getFieldType()).append(");");
                    } else {
                        ondblclick = new StringBuffer("doCallFieldUpdateInCurrentLocale('")
                                .append(locale).append("', ")
                                .append(fieldId).append(", '")
                                .append(requestUrl).append("');");
                    }
                    StringBuffer oncontextmenu = new StringBuffer("return showContextMenu(").append(contextMenuLayerVar).append(", event);");

					String divDisplay;
                    if (DISPLAY_AUTO.equals(wrapperDisplay)) {
                        divDisplay = DISPLAY_INLINE;
                        if ( fieldType == ContentField.HTML_TYPE ) {
                            divDisplay = DISPLAY_BLOCK;
                        }
                    } else {
                        divDisplay = wrapperDisplay;
                    }
                    StringBuffer buffer = new StringBuffer("<div id=\"").append(divId)
                            .append("\" name=\"").append(divId)
                            .append("\" class=\"fieldValueWrapper\" style=\"display: ")
                            .append(divDisplay).append(";\" ")
                            .append("onmouseover=\"").append(onmouseover)
                            .append("\" onmouseout=\"").append(onmouseout)
                            .append("\" ondblclick=\"").append(ondblclick);
                    if (!fieldNotFound) {
                        buffer.append("\" oncontextmenu=\"").append(oncontextmenu);
                    }
                    buffer.append("\">").append(content).append("</div>");
                    content = buffer.toString();
					tagUtils.write(pageContext, content);

					// Insert context menu
					// Obtain titles
					String editInCurrentLocaleTitle = applicationResources.getMessage(request,
                            "core.editMode.menu.context.editInCurrentLocale");
					String editInAllLocalesTitle = applicationResources.getMessage(request,
                            "core.editMode.menu.context.editInAllLocales");
					String editInChosenLocalesTitle = applicationResources.getMessage(request,
                            "core.editMode.menu.context.editInChosenLocales");
                    String createTitle = null;
                    String deleteTitle = null;
                    if (indexed && !fieldNotFound) {
                        createTitle = applicationResources.getMessage(request,
                                "core.editMode.menu.context.create");
                        deleteTitle = applicationResources.getMessage(request,
                                "core.editMode.menu.context.delete");
                    }

                    String normalClass
                            = WebappConstants.CONTEXT_MENU_ITEM_STYLE_CLASS;
                    String hltdClass
                            = WebappConstants.HIGHLIGHTED_CONTEXT_MENU_ITEM_STYLE_CLASS;

                    // Create and populate list of items
					ContextMenuItem menu = new ContextMenuItem();
					menu.setId(contextMenuId.toString());
                    menu.addChildItem(new ContextMenuItem(JavaScriptUtil.createLinkAsLayer("javascript:" + ondblclick,
					        editInCurrentLocaleTitle, normalClass, hltdClass)));
					menu.addChildItem(new ContextMenuItem(JavaScriptUtil.createLinkAsLayer("javascript:doCallFieldUpdate(" + fieldId + ", '" + requestUrl + "');",
					        editInAllLocalesTitle, normalClass, hltdClass)));
					menu.addChildItem(new ContextMenuItem(JavaScriptUtil.createLinkAsLayer("javascript:doChooseLocalesForFieldUpdate(" + fieldId + ", '" + requestUrl + "');",
					        editInChosenLocalesTitle, normalClass, hltdClass)));
                    if (indexed && !fieldNotFound) {
                        menu.addChildItem(new ContextMenuItem(JavaScriptUtil.createLinkAsLayer("javascript:doCallCreateIndexedField(" + ownerId + ", '" + indexlessIdentifier + "', " + index + ", " + fieldType + ", '" + definition + "', '" + requestUrl + "');",
	    				        createTitle, normalClass, hltdClass)));
                        menu.addChildItem(new ContextMenuItem(JavaScriptUtil.createLinkAsLayer("javascript:doDeleteIndexedField(" + fieldId + ", " + ownerId + ", '" + requestUrl + "');",
	    				        deleteTitle, normalClass, hltdClass)));
                    }

                    // Store menu in request
					pageContext.setAttribute(WebappConstants.CONTEXT_MENUS_KEY,
                            Arrays.asList(new ContextMenuItem[]{menu}),
                            PageContext.REQUEST_SCOPE);

					// include page that renders menu
					pageContext.include(contextMenuJsp);
				} else {
					// edit mode disabled OR content is not editable OR field
                    // not present and page is not content page
                    showEmpty = this.showEmpty == null
                            ? fieldType != ContentField.LINE_TYPE
                            : this.showEmpty.booleanValue();
					if ( content == null ) {
						content = "";
					}
					if (fieldNotFound && !ignore.booleanValue()) {
                        tagUtils.write(pageContext, applicationResources.getMessage(request, "core.commons.contentTag.fieldNotFound", new Object[]{identifier, savedUri, savedDefinition}));
                    } else if ( !contentIsEmpty || showEmpty ) {
						tagUtils.write(pageContext, content);
					}
				}
			}
		} catch ( Exception ex ) {
            throw new JspTagException(ex);
		}

	}


	/**
	 * Searches for content in given field value according to field type
	 *
	 * @param fieldValue Content field value to search content in
	 * @param fieldType  Type of field which is owner of given field value
	 * @return Content or
	 */
	protected String retrieveContent(ContentFieldValue fieldValue,
                                     byte fieldType) {
		String content;
		if ( fieldType == ContentField.LINE_TYPE ) {
			content = fieldValue.getSimpleValue();
		} else {
			content = ConvertUtil.convertToString(fieldValue.getValue());
		}
		return content;
	}

}
